; 512-byte boot sector that loads the TinyGo kernel without GRUB.

BITS 16
ORG 0x7C00

%ifndef KERNEL_SECTORS
    %error "KERNEL_SECTORS not defined"
%endif

STACK_SEG        equ 0x9000
STACK_OFFSET     equ 0x0000
KERNEL_LOAD_SEG  equ 0x1000
KERNEL_LOAD_OFF  equ 0x0000
KERNEL_START     equ (KERNEL_LOAD_SEG << 4) + KERNEL_LOAD_OFF

start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00

    mov [boot_drive], dl

    call enable_a20
    call load_kernel
    call enter_protected_mode

.hang:
    hlt
    jmp .hang

; -----------------------------------------------------------------------------
; Enable A20 using keyboard controller method.
; -----------------------------------------------------------------------------
enable_a20:
    in al, 0x92
    test al, 2
    jnz .done
    or al, 2
    and al, 0xFE
    out 0x92, al
.wait:
    in al, 0x92
    test al, 2
    jz .wait
.done:
    ret

; -----------------------------------------------------------------------------
; Load kernel sectors into memory starting at KERNEL_LOAD_SEG:KERNEL_LOAD_OFF.
; -----------------------------------------------------------------------------
load_kernel:
    mov ax, KERNEL_LOAD_SEG
    mov es, ax
    xor bx, bx

    mov si, disk_address_packet
    mov word [si + 2], KERNEL_SECTORS
    mov word [si + 4], bx
    mov word [si + 6], ax

    mov dl, [boot_drive]
    mov ah, 0x42
    int 0x13
    jc disk_error
    ret

disk_error:
    mov si, msg_disk_error
    call bios_print
    jmp $

; -----------------------------------------------------------------------------
; Switch to 32-bit protected mode and jump to the kernel entry point.
; -----------------------------------------------------------------------------
enter_protected_mode:
    cli
    lgdt [gdt_descriptor]

    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp CODE_SEL:protected_entry

[BITS 32]
protected_entry:
    mov ax, DATA_SEL
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, STACK_SEG << 4

    call KERNEL_START

.halt:
    hlt
    jmp .halt

[BITS 16]

; -----------------------------------------------------------------------------
; BIOS teletype print routine (null-terminated string at DS:SI).
; -----------------------------------------------------------------------------
bios_print:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E
    mov bh, 0x00
    mov bl, 0x07
    int 0x10
    jmp bios_print
.done:
    ret

; -----------------------------------------------------------------------------
; Data
; -----------------------------------------------------------------------------
boot_drive: db 0

msg_disk_error: db "Disk read error", 13, 10, 0

disk_address_packet:
    db 0x10                ; Packet size
    db 0x00
    dw KERNEL_SECTORS      ; Will be patched before call
    dw 0x0000              ; Buffer offset
    dw KERNEL_LOAD_SEG     ; Buffer segment
    dq 0x0000000000000001  ; Starting LBA (sector 1)

gdt_start:
    dq 0x0000000000000000
    dq 0x00CF9A000000FFFF  ; Code segment
    dq 0x00CF92000000FFFF  ; Data segment
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

CODE_SEL equ 0x08
DATA_SEL equ 0x10

times 510 - ($ - $$) db 0
dw 0xAA55
